﻿using System.Security.Cryptography;
using gixoom.Dom.Converters;
using System;

namespace gixoom.Dom.Score
{
    internal class Sha512HashFacade: IScoreFacade
    {
        private const int hashSizeBytes = 64;

        private SHA512 algorithm;
        private ISha512StringValidator validator;
        private IHexDigitsStringToBytesConverter converter;

        /// <summary>
        /// Creates Sha512HashFacade object.
        /// </summary>
        /// <param name="algorithm">algorithm for calculating SHA512 hash</param>
        /// <param name="validator">SHA512 string validator</param>
        /// <param name="converter">hexadecimal digits to bytes converter</param>
        /// <exception cref="System.ArgumentNullException">One of the arguments is a null reference.</exception>
        public Sha512HashFacade(SHA512 algorithm, ISha512StringValidator validator,
            IHexDigitsStringToBytesConverter converter)
        {
            Ensure.ArgumentNotNull(algorithm, "algorithm");
            Ensure.ArgumentNotNull(validator, "validator");
            Ensure.ArgumentNotNull(converter, "converter");

            this.algorithm = algorithm;
            this.validator = validator;
            this.converter = converter;
        }

        /// <summary>
        /// Creates score object from the specified content.
        /// </summary>
        /// <param name="content">the content to create the score object for</param>
        /// <returns>created score object</returns>
        /// <exception cref="System.ArgumentNullException"><i>content</i> is a null reference.</exception>
        public IScore CreateScoreFromContent(byte[] content)
        {
            Ensure.ArgumentNotNull(content, "content");

            var score = new Sha512Hash(content, algorithm);
            return score;
        }

        /// <summary>
        /// Creates score object from the specified range of the specified array.
        /// </summary>
        /// <param name="content">the input to create score object for</param>
        /// <param name="offset">the offset into the byte array from which to begin using data</param>
        /// <param name="count">the number of bytes in the array to use as data</param>
        /// <returns>created score object</returns>
        /// <exception cref="System.ArgumentNullException"><i>content</i> is a null reference</exception>
        /// <exception cref="System.ArgumentOutOfRangeException"><i>offset</i> is out of range.
        /// This parameter requires a non-negative number which is less than <i>content</i> length.</exception>
        /// <exception cref="System.ArgumentException"><i>count</i> is an invalid value or <i>content</i> length 
        /// is invalid.</exception>
        public IScore CreateScoreFromContent(byte[] content, int offset, int count)
        {
            Ensure.ArgumentNotNull(content, "content");
            Ensure.IndexWithinRange(offset, content, "offset");
            Ensure.OffsetAndCountAreValid(offset, count, content, "count", "content");

            var score = new Sha512Hash(content, offset, count, algorithm);

            return score;
        }

        /// <summary>
        /// Creates score object from the specified string.
        /// </summary>
        /// <param name="hashString">hash string</param>
        /// <returns>created score object</returns>
        /// <exception cref="System.ArgumentNullException"><i>hashString</i> is a null reference.</exception>
        /// <exception cref="System.ArgumentException"><i>hashString</i> is empty or invalid.</exception>
        public IScore CreateScoreFromString(string hashString)
        {
            Ensure.ArgumentNotNull(hashString, "hashString");
            Ensure.StringArgumentNotEmpty(hashString, "hashString");
            Ensure.Sha512HashStringIsValid(hashString, validator, "hashString");

            var score = new Sha512Hash(hashString, converter);
            return score;
        }

        /// <summary>
        /// Creates score object from the specified array.
        /// </summary>
        /// <param name="bytes">the array to create score object from</param>
        /// <returns>created score object</returns>
        /// <exception cref="System.ArgumentNullException"><i>bytes</i> is a null reference.</exception>
        /// <exception cref="System.ArgumentException"><i>bytes</i> length is invalid.</exception>
        public IScore CreateScoreFromBytes(byte[] bytes)
        {
            Ensure.ArgumentNotNull(bytes, "bytes");
            Ensure.ArrayLengthIsValid(bytes, hashSizeBytes, "bytes");

            var score = new Sha512Hash(bytes);

            return score;
        }

        /// <summary>
        /// Creates score object from the specified <i>bytes</i> array starting
        /// with the specified index.
        /// </summary>
        /// <param name="bytes">the input to create score object from</param>
        /// <param name="bytesIndex">the index in the array at which the bytes will be read</returns>
        /// <exception cref="System.ArgumentNullException"><i>bytes</i> is a null reference.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException"><i>bytesIndex</i> doesn't fall into
        /// the <i>bytes</i> boundaries.</exception>
        /// <exception cref="System.ArgumentException"><i>bytes</i> length or <i>bytesIndex</i> is invalid.</exception>
        public IScore CreateScoreFromBytes(byte[] bytes, int bytesIndex)
        {
            Ensure.ArgumentNotNull(bytes, "bytes");
            Ensure.IndexWithinRange(bytesIndex, bytes, "bytesIndex");
            Ensure.EnoughItemsTillEnd(bytes, bytesIndex, hashSizeBytes, "bytesIndex", "bytes");

            var scoreBytes = new byte[hashSizeBytes];
            Array.Copy(bytes, bytesIndex, scoreBytes, 0, hashSizeBytes);
            var score = new Sha512Hash(scoreBytes);

            return score;
        }

        /// <summary>
        /// Gets the number of bytes in score (it is the fixed value).
        /// </summary>
        public int ScoreSizeBytes
        {
            get
            {
                return hashSizeBytes;
            }
        }
    }
}
